/**

    Custom browser faking object for code-validation
    in automated test settings.

**/

module.exports = (function fakeBrowser() {
  // Processing works with a canvas and the DOM, fake it.
  // This is enough of the DOM to allow the parser work.
  var __empty_func__ = function () {};
  var __elem_func__ = function() { return elem; };

  var navigator = { useragent: true };

  // generic HTML element
  var createElement = function(tag) {
    return {
      localName: tag,
      style: {},
      setAttribute: __empty_func__,
      appendChild:  __empty_func__,
      classList: {
        add: __empty_func__,
        remove: __empty_func__,
        contains: __empty_func__
      }
    };
  };

  var HTMLCanvasElement = function() {};

  // HTML canvas element
  var Canvas = function() {
    this.attachEvent = __empty_func__;
    this.addEventListener = __empty_func__;
    this.appendChild = __elem_func__;
    this.removeChild = __empty_func__;
    this.childNodes = { length: 0 };
    this.toDataURL = __empty_func__;
    this.localName = "canvas";
    this.width = 100;
    this.height = 100;
    this.getContext = function() {
                     return {
                       translate: __empty_func__,
                       attachEvent: __empty_func__,
                       fillRect: __empty_func__,
                       strokeRect: __empty_func__,
                       fillText: __empty_func__,
                       measureText: function() { return {width:1}; },
                       clearRect: __empty_func__,
                       beginPath: __empty_func__,
                       moveTo: __empty_func__,
                       lineTo: __empty_func__,
                       rect: __empty_func__,
                       save: __empty_func__,
                       stroke: __empty_func__,
                       fill: __empty_func__,
                       rotate: __empty_func__,
                       closePath: __empty_func__,
                       arc: __empty_func__,
                       scale: __empty_func__,
                       restore: __empty_func__,
                       bezierCurveTo: __empty_func__,
                       viewport: __empty_func__,
                       clearColor: __empty_func__,
                       clear: __empty_func__,
                       enable: __empty_func__,
                       createShader: __empty_func__,
                       shaderSource: __empty_func__,
                       compileShader: __empty_func__,
                       getShaderParameter: function() { return true; },
                       getShaderInfoLog: __empty_func__,
                       createProgram: __empty_func__,
                       attachShader: __empty_func__,
                       linkProgram: __empty_func__,
                       getProgramParameter: function() { return true; },
                       useProgram: __empty_func__,
                       createBuffer: __empty_func__,
                       bindBuffer: __empty_func__,
                       bufferData: __empty_func__,
                       blendFunc: __empty_func__,
                       getAttribLocation: __empty_func__,
                       vertexAttribPointer: __empty_func__,
                       enableVertexAttribArray: __empty_func__,
                       getUniformLocation: __empty_func__,
                       uniform1f: __empty_func__,
                       uniform2f: __empty_func__,
                       uniform3f: __empty_func__,
                       uniform4f: __empty_func__,
                       uniformfv: __empty_func__,
                       uniform2fv: __empty_func__,
                       uniform3fv: __empty_func__,
                       uniform1i: __empty_func__,
                       uniform2i: __empty_func__,
                       uniform3i: __empty_func__,
                       uniform4i: __empty_func__,
                       getImageData: function() { return {width:1, height:1, data:[1,2,3,4]}; },
                       createImageData: function() { return {width:1, height:1, data:[1,2,3,4]}; },
                       drawImage: __empty_func__,
                       drawElements: __empty_func__,
                       putImageData: __empty_func__,
                       lineWidth: __empty_func__,
                       disable: __empty_func__,
                       drawArrays: __empty_func__,
                       polygonOffset: __empty_func__,
                       createTexture: __empty_func__,
                       texImage2D: __empty_func__,
                       texParameteri: __empty_func__,
                       generateMipmap: __empty_func__,
                       uniformMatrix4fv: __empty_func__,
                       uniform4fv: __empty_func__,
                       disableVertexAttribArray: __empty_func__,
                       bindTexture: __empty_func__,
                       setTransform: __empty_func__
                     };
    };
    this.style = {
      setProperty: __empty_func__
    };
    this.__attributes = { "data-processing-sources" : "test.pjs" };
    this.hasAttribute = function(name) { return this.__attributes.hasOwnProperty(name.toLowerCase()); };
    this.getAttribute = function(name) { return this.__attributes[name.toLowerCase()]; };
    this.setAttribute = function(name, value) { this.__attributes[name.toLowerCase()] = value; };
  };
  Canvas.prototype = new HTMLCanvasElement();

  var canvas = new Canvas();

  var HTMLImageElement = __empty_func__;

  var document = {
    fake: true,
    attachEvent: __empty_func__,
    head: {
      appendChild: __elem_func__,
      removeChild: __empty_func__
    },
    body: {
      appendChild: __elem_func__,
      removeChild: __empty_func__,
      style: {
        cursor: {}
      }
    },
    querySelector: function(selector) {
      return createElement(selector);
    },
    defaultView: {
      getComputedStyle: function() {
        return { getPropertyValue: function() { return ""; } };
      }
    },
    appendChild: __elem_func__,
    removeChild: __empty_func__,
    getElementById: __empty_func__,
    getElementsByTagName: function() { return [canvas]; },
    createElement: function (tag) {
      if (tag === "canvas") return new Canvas();
      return createElement(tag);
    },
    addEventListener: __empty_func__,
    documentElement: {
      appendChild: __elem_func__,
      removeChild: __empty_func__,
      style: {
        paddingBottom: 0
      },
      insertBefore: __empty_func__
    },
    createTextNode: __empty_func__
  };

  var elem = {
    appendChild: __elem_func__,
    removeChild: __empty_func__,
    style: {}
  };

  var addEventListener = __empty_func__;
  var XMLHttpRequest = __empty_func__;
  var setInterval = __empty_func__;
  var clearInterval = __empty_func__;

  var Image = __empty_func__;

  var localStorage = __empty_func__;

  // The XML parser is designed only for parsing of
  // simple XML documents (for unit testing purpose).
  function DOMParser() {
    // parser
    function parseXml(s, sink) {
      var i = 0, scopes = [{
        space: "default",
        xmlns: "",
        namespaces: {
          "xmlns":"http://www.w3.org/2000/xmlns/",
          "xml":"http://www.w3.org/XML/1998/namespace"
        }
      }];

      function unSelfClose(s) {
        // This code is actually not good at parsing self-closed elements,
        // so as a preprocess step we "open" self closed elements.
        return s.replace(/<([a-zA-Z-_]+)[^\/>]*\/>/g, function(a,b) {
          if(a==="undefined" && b==="undefined") return '';
          return a.replace(/\s*\/>/, "></"+b+">");
        });
      }

      function trim(s) {
        return s.replace(/^\s+/, "").replace(/\s+$/, "");
      }

      function resolveEntities(s) {
        return s.replace(/&([^;]+);/g, function(all, entity) {
          if (entity.substring(0, 2) === "#x") {
            return String.fromCharCode(parseInt(entity.substring(2), 16));
          } else if(entity.substring(0,1) === "#") {
            return String.fromCharCode(parseInt(entity.substring(1), 10));
          }
          switch (entity) {
          case "lt": return "<";
          case "gt": return ">";
          case "amp": return "&";
          }
          throw "Unknown entity: " + entity;
        });
      }

      function isWhitespacePreserved() {
        for (var j = scopes.length - 1; j >= 0; --j) {
          if (scopes[j].space === "preserve") {
            return true;
          }
        }
        return false;
      }

      function lookupDefaultNs() {
        for (var j = scopes.length - 1; j >= 0; --j) {
          if (scopes[j].hasOwnProperty("xmlns")) {
            return scopes[j].xmlns;
          }
        }
      }

      function lookupNs(prefix) {
        for (var j = scopes.length - 1; j >= 0; --j) {
          if (scopes[j].namespaces.hasOwnProperty(prefix)) {
            return scopes[j].namespaces[prefix];
          }
        }
        throw "Unknow namespace: " + prefix;
      }

      function getName(name, resolveDefaultNs) {
        var j = name.indexOf(":");
        if (j >= 0) {
          return {name:name.substring(j + 1), prefix: name.substring(0,j), namespace: lookupNs(name.substring(0,j))};
        } else if(resolveDefaultNs) {
          return {name:name, prefix: "", namespace: lookupDefaultNs()};
        } else {
          return {name:name, prefix: "", namespace: ""};
        }
      }

      function isWhitespace(s, index) {
        var ch = s.charCodeAt(index);
        return ch == 10 || ch == 13 || ch == 9 || ch == 32;
      }

      function parseContent(s, start) {
        var pos = start, name, attributes = [];
        function skipWs() {
          while (pos < s.length && isWhitespace(s, pos)) {
            ++pos;
          }
        }
        while (pos < s.length && !isWhitespace(s, pos) && s.charAt(pos) !== ">") {
          ++pos;
        }
        name = s.substring(start, pos);
        skipWs();
        while (pos < s.length && s.charAt(pos) !== ">" &&
               s.charAt(pos) !== "/" && s.charAt(pos) !== "?") {
          skipWs();
          var attrName = "", attrValue = "";
          while (pos < s.length && !isWhitespace(s, pos) && s.charAt(pos) !== "=") {
            attrName += s.charAt(pos);
            ++pos;
          }
          skipWs();
          if (s.charAt(pos) !== "=") throw "'=' expected";
          ++pos;
          skipWs();
          var attrEndChar = s.charAt(pos);
          if (attrEndChar !== "\"" && attrEndChar !== "\'" ) throw "Quote expected";
          var attrEndIndex = s.indexOf(attrEndChar, ++pos);
          if (attrEndIndex < 0) throw "Unexpected EOF[6]";
          attrValue = s.substring(pos, attrEndIndex);
          attributes.push({name: attrName, value: resolveEntities(attrValue)});
          pos = attrEndIndex + 1;
          skipWs();
        }
        return {name: name, attributes: attributes, parsed: pos - start};
      }

      // ==================================
      // The actual XML parsing starts here
      // ==================================

      s = unSelfClose(s);
      while (i < s.length) {
        var ch = s.charAt(i);
        var j = i;
        var content;
        if (ch === "<") {
          ++j;
          var ch2 = s.charAt(j), q, name;
          switch (ch2) {
          case "/":
            ++j;
            q = s.indexOf(">", j); if(q < 0) { throw "Unexpected EOF[1]"; }
            name = getName(s.substring(j,q), true);
            sink.endElement(name);
            scopes.pop();
            j = q + 1;
            break;
          case "?":
            ++j;
            content = parseContent(s, j);
            if (s.substring(j + content.parsed, j + content.parsed + 2) != "?>") {
              throw "Unexpected EOF[2]";
            }
            sink.pi(content.name, content.attributes);
            j += content.parsed + 2;
            break;
          case "!":
            if (s.substring(j + 1, j + 3) === "--") {
              q = s.indexOf("-->", j + 3); if(q < 0) { throw "Unexpected EOF[3]"; }
              sink.comment(s.substring(j + 3, q));
              j = q + 3;
            } else if (s.substring(j + 1, j + 8) === "[CDATA[") {
              q = s.indexOf("]]>", j + 8); if(q < 0) { throw "Unexpected EOF[4]"; }
              sink.cdata(s.substring(j + 8, q));
              j = q + 3;
            } else if (s.substring(j + 1, j + 8) === "DOCTYPE") {
              var q2 = s.indexOf("[", j + 8), complexDoctype = false;
              q = s.indexOf(">", j + 8); if(q < 0) { throw "Unexpected EOF[5]"; }
              if (q2 > 0 && q > q2) {
                q = s.indexOf("]>", j + 8); if(q < 0) { throw "Unexpected EOF[7]"; }
                complexDoctype = true;
              }
              var doctypeContent = s.substring(j + 8, q + (complexDoctype ? 1 : 0));
              sink.doctype(doctypeContent);
              // XXX pull entities ?
              j = q + (complexDoctype ? 2 : 1);
            } else {
              throw "Unknown !tag";
            }
            break;
          default:
            content = parseContent(s, j);
            var isClosed = false;
            if (s.substring(j + content.parsed, j + content.parsed + 2) === "/>") {
              isClosed = true;
            } else if (s.substring(j + content.parsed, j + content.parsed + 1) !== ">") {
              throw "Unexpected EOF[2]";
            }
            var scope = {namespaces:[]};
            for (q = 0; q < content.attributes.length; ++q) {
              if (content.attributes[q].name.substring(0, 6) === "xmlns:") {
                scope.namespaces[content.attributes[q].name.substring(6)] = trim(content.attributes[q].value);
              } else if (content.attributes[q].name === "xmlns") {
                scope.xmlns = trim(content.attributes[q].value);
              } else if (content.attributes[q].name.substring(0, 4) === "xml:") {
                scope[content.attributes[q].name.substring(4)] = trim(content.attributes[q].value);
              } else if (content.attributes[q].name.substring(0, 3) === "xml") {
                throw "Invalid xml attribute";
              }
            }
            scopes.push(scope);
            var attributes = [];
            for (q = 0; q < content.attributes.length; ++q) {
              attributes.push({name: getName(content.attributes[q].name, false), value: content.attributes[q].value});
            }
            sink.beginElement(getName(content.name, true), attributes, isClosed);
            j += content.parsed + (isClosed ? 2 : 1);
            if (isClosed) scopes.pop();
            break;
          }
        } else {
          do {
            if (++j >= s.length) break;
          } while(s.charAt(j) !== "<");
          var text = s.substring(i, j);
          var isWs = text.replace(/^\s+/, "").length === 0;
          if (!isWs || isWhitespacePreserved()) {
            sink.text(resolveEntities(text), isWs);
          }
        }
        i = j;
      }
    }
    // end of parser

    var implementation = this;

    function NodeList(init) {
      var nodes = [];
      if (init) {
        nodes = nodes.concat(init);
      }
      nodes.item = function(index) { return nodes[index]; };
      return nodes;
    }
    function NamedNodeMap(owner) {
      var nodes = [];
      nodes.item = function(index) { return nodes[index]; };
      nodes.getNamedItem = function(name) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].name === name) return nodes[i];
        }
        return null;
      };
      nodes.setNamedItem = function(arg) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].name === arg.name) {
            var old = nodes[i];
            nodes[i] = arg;
            old.ownerElement = null;
            return old;
          }
        }
        arg.ownerElement = owner;
        nodes.push(arg);
        return null;
      };
      nodes.removeNamedItem = function(name) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].name === name) {
            var old = nodes[i];
            nodes.splice(i, 1);
            old.ownerElement = null;
            return old;
          }
        }
        return null;
      };
      nodes.getNamedItemNS = function(namespaceURI, localName) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].localName === localName &&
              nodes[i].namespaceURI === namespaceURI) return nodes[i];
        }
        return null;
      };
      nodes.setNamedItemNS = function(arg) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].localName === arg.localName &&
              nodes[i].namespaceURI === arg.namespaceURI) {
            var old = nodes[i];
            nodes[i] = arg;
            old.ownerElement = null;
            return old;
          }
        }
        arg.ownerElement = owner;
        nodes.push(arg);
        return null;
      };
      nodes.removeNamedItemNS = function(namespaceURI, localName) {
        for (var i = 0; i < nodes.length; ++i) {
          if (nodes[i].localName === localName &&
              nodes[i].namespaceURI === namespaceURI) {
            var old = nodes[i];
            nodes.splice(i, 1);
            old.ownerElement = null;
            return old;
          }
        }
        return null;
      };
      return nodes;
    }
    function Node() {
      this.childNodes = new NodeList();
    }
    Node.ELEMENT_NODE                   = 1;
    Node.ATTRIBUTE_NODE                 = 2;
    Node.TEXT_NODE                      = 3;
    Node.CDATA_SECTION_NODE             = 4;
    Node.ENTITY_REFERENCE_NODE          = 5;
    Node.ENTITY_NODE                    = 6;
    Node.PROCESSING_INSTRUCTION_NODE    = 7;
    Node.COMMENT_NODE                   = 8;
    Node.DOCUMENT_NODE                  = 9;
    Node.DOCUMENT_TYPE_NODE             = 10;
    Node.DOCUMENT_FRAGMENT_NODE         = 11;
    Node.NOTATION_NODE                  = 12;
    Node.prototype.nodeName = null;
    Node.prototype.nodeValue = null;
    Node.prototype.nodeType = 0;
    Node.prototype.parentNode = null;
    //Node.prototype.childNodes = [];
    Node.prototype.firstChild = null;
    Node.prototype.lastChild = null;
    Node.prototype.previousSibling = null;
    Node.prototype.nextSibling = null;
    Node.prototype.ownerDocument = null;
    Node.prototype.attributes = null;
    Node.prototype.onChildNodesChanged = function() {};
    // XXX newChild in tree remove first
    // XXX document fragment
    Node.prototype.insertBefore = function(newChild, refChild) {
      if (!refChild) {
        return this.appendChild(newChild);
      }
      var i = 0;
      while (i < this.childNodes.length && this.childNodes[i] != refChild) {
        ++i;
      }
      if (!this.childNodes[i]) throw "Ref child not found";
      newChild.parentNode = this;
      newChild.previousSibling = refChild.previousSibling;
      if (newChild.previousSibling) newChild.previousSibling.nextSibling = newChild;
      newChild.nextSibling = refChild;
      refChild.previousSibling = newChild;
      if (this.firstChild == refChild) this.firstChild = newChild;
      this.childNodes.splice(i,0,newChild);
      this.onChildNodesChanged();
      return newChild;
    };
    Node.prototype.replaceChild = function(newChild, oldChild) {
      var i = 0;
      while (i < this.childNodes.length && this.childNodes[i] != oldChild) {
        ++i;
      }
      if (!this.childNodes[i]) throw "Old child not found";
      newChild.parentNode = this;
      newChild.previousSibling = oldChild.previousSibling;
      if (newChild.previousSibling) newChild.previousSibling.nextSibling = newChild;
      newChild.nextSibling = oldChild.nextSibling;
      if (newChild.nextSibling) newChild.nextSibling.previousSibling = newChild;
      oldChild.parentNode = null;
      oldChild.previousSibling = null;
      oldChild.nextSibling = null;
      if (this.lastChild == oldChild) this.lastChild = newChild;
      if (this.firstChild == oldChild) this.firstChild = newChild;
      this.childNodes[i] = newChild;
      this.onChildNodesChanged();
      return oldChild;
    };
    Node.prototype.removeChild = function(oldChild) {
      var i = 0;
      while (i < this.childNodes.length && this.childNodes[i] != oldChild) {
        ++i;
      }
      if (!this.childNodes[i]) throw "Old child not found";
      if (this.lastChild == oldChild) this.lastChild = oldChild.previousSibling;
      if (this.firstChild == oldChild) this.firstChild = oldChild.nextSibling;
      if (oldChild.nextSibling) oldChild.nextSibling.previousSibling = oldChild.previousSibling;
      if (oldChild.previousSibling) oldChild.previousSibling.nextSibling = oldChild.nextSibling;
      oldChild.parentNode = null;
      oldChild.previousSibling = null;
      oldChild.nextSibling = null;
      this.childNodes.splice(i, 1);
      this.onChildNodesChanged();
      return oldChild;
    };
    Node.prototype.appendChild = function(newChild) {
      newChild.parentNode = this;
      var lastChild = this.lastChild;
      if (lastChild) {
        newChild.nextSibling = lastChild.nextSibling;
        if (newChild.nextSibling) newChild.nextSibling.previousSibling = newChild;
        newChild.previousSibling = lastChild;
        lastChild.nextSibling = newChild;
      } else {
        this.firstChild = newChild;
        newChild.previousSibling = null;
        newChild.nextSibling = null;
      }
      this.lastChild = newChild;
      this.childNodes.push(newChild);
      this.onChildNodesChanged();
      return newChild;
    };
    Node.prototype.hasChildNodes = function() {
      return this.childNodes.length > 0;
    };
    Node.prototype.cloneNode = function(deep) {
      throw "Not implemented";
    };
    Node.prototype.normalize = function() {
      var lastWasText = false;
      for (var i = 0; i < this.childNodes.length; ++i) {
        if (this.childNodes[i].nodeType == Node.ELEMENT_NODE) {
          this.childNodes[i].normalize();
        }
        if (this.childNodes[i].nodeType == Node.TEXT_NODE) {
          if (lastWasText) {
            this.childNodes[i - 1].appendData(this.childNodes[i].data);
            this.removeChild(this.childNodes[i]);
            --i;
          }
          lastWasText = true;
        } else {
          lastWasText = false;
        }
      }
    };
    Node.prototype.isSupported = function(feature, version) {
      return false;
    };
    Node.prototype.namespaceURI = null;
    Node.prototype.prefix = null;
    Node.prototype.localName = null;
    Node.prototype.hasAttributes = function() {
      return this.attributes.length > 0;
    };

    Node.prototype.textContent = "";

    function Document() {
      var node = new Node();
      node.nodeType = Node.DOCUMENT_NODE;
      node.doctype = null;
      node.implementation = implementation;
      node.documentElement = null;
      node.createElement = function(tagName) {
        return node.createElementNS("", tagName);
      };
      node.createTextNode = function(data, nodeTypeOverride) {
        var text = new Node();
        text.nodeType = (nodeTypeOverride ? nodeTypeOverride : Node.TEXT_NODE);
        text.ownerDocument = node;
        text.nodeValue = data;
        Object.defineProperty(text, "data", {
          get: function() { return this.nodeValue; },
          set: function(value) { this.nodeValue = value; },
          enumerable: true
        });

        text.data = data;
        text.length = data.length;
        text.appendData = function(arg) {
          this.nodeValue += arg;
        };

        Object.defineProperty(text, "textContent", {
          get: function() { return this.nodeValue; },
          enumerable: true
        });
        // TODO appendData, substringData, etc.
        return text;
      };
      node.createCDATASection = function(data) {
        return this.createTextNode(data, Node.CDATA_SECTION_NODE);
      };
      node.createAttribute = function(name) {
        return node.createAttributeNS("", name);
      };
      node.getElementsByTagName = function(tagName) {
        return node.getElementsByTagNameNS("", tagName);
      };
      node.createElementNS = function(namespaceURI, qualifiedName) {
        var element = new Node();
        element.nodeType = Node.ELEMENT_NODE;
        element.ownerDocument = node;
        element.attributes = new NamedNodeMap(element);
        element.tagName = qualifiedName;
        element.nodeName = qualifiedName;
        var i = qualifiedName.indexOf(":");
        element.localName = i < 0 ? qualifiedName : qualifiedName.substring(i + 1);
        element.prefix = i < 0 ? "" : qualifiedName.substring(0, i);
        element.namespaceURI = namespaceURI;
        element.getAttributeNode = function(name) {
          return this.attributes.getNamedItem(name);
        };
        element.getAttribute = function(name) {
          var attr = this.getAttributeNode(name);
          return attr ? attr.value : "";
        };
        element.setAttributeNode = function(newAttr) {
          return this.attributes.setNamedItem(newAttr);
        };
        element.setAttribute = function(name, value) {
          var attr = this.ownerDocument.createAttribute(name);
          attr.value = value || "";
          this.setAttributeNode(attr);
        };
        element.removeAttribute = function(name) {
          this.attributes.removeNamedItem(name);
        };
        element.removeAttributeNode = function(oldAttr) {
          return this.attributes.removeNamedItem(oldAttr.name);
        };
        element.hasAttribute = function(name) {
          return !!this.attributes.getNamedItem(name);
        };
        element.getAttributeNodeNS = function(namespaceURI, localName) {
          return this.attributes.getNamedItemNS(namespaceURI, localName);
        };
        element.getAttributeNS = function(namespaceURI, localName) {
          var attr = this.getAttributeNodeNS(namespaceURI, localName);
          return attr ? attr.value : "";
        };
        element.setAttributeNodeNS = function(newAttr) {
          return this.attributes.setNamedItemNS(newAttr);
        };
        element.setAttribute = function(namespaceURI, qualifiedName) {
          var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
          attr.value = value || "";
          this.setAttributeNodeNS(attr);
        };
        element.removeAttributeNS = function(namespaceURI, localName) {
          this.attributes.removeNamedItemNS(namespaceURI, localName);
        };
        element.removeAttributeNode = function(oldAttr) {
          return this.attributes.removeNamedItemNS(oldAttr.namespaceNS, oldAttr.localName);
        };
        element.hasAttributeNS = function(namespaceURI, localName) {
          return !!this.attributes.getNamedItemNS(namespaceURI, localName);
        };
        element.getElementsByTagName = function(name) {
          return this.getElementsByTagNameNS("", name);
        };
        element.getElementsByTagNameNS = function(namespaceURI, localName) {
          var q = [this];
          var result = [];
          while (q.length > 0) {
            var current = q.shift();
            if (current.nodeType == Node.ELEMENT_NODE) {
              if (current.namespaceURI == namespaceURI && current.localName == localName) {
                result.push(current);
              }

              for (var i = 0; i < current.childNodes.length; ++i) {
                q.push(current.childNodes[i]);
              }
            }
          }
          return new NodeList(result);
        };

        Object.defineProperty(element, "textContent", {
          get: function() {
            var result = "";
            for (var i = 0; i < this.childNodes.length; ++i) {
              result += this.childNodes[i].textContent;
            }
            return result;
          },
          enumerable: true
        });

        return element;
      };
      node.createAttributeNS = function(namespaceURI, qualifiedName) {
        var attr = new Node();
        attr.nodeType = Node.ATTRIBUTE_NODE;
        attr.ownerDocument = node;
        attr.name = qualifiedName;
        attr.nodeName = qualifiedName;
        var i = qualifiedName.indexOf(":");
        attr.localName = i < 0 ? qualifiedName : qualifiedName.substring(i + 1);
        attr.prefix = i < 0 ? "" : qualifiedName.substring(0, i);
        attr.namespaceURI = namespaceURI;
        attr.specified = true;
        attr.nodeValue = null;
        Object.defineProperty(attr, "value", {
          get: function() { return this.nodeValue; },
          set: function(value) { this.nodeValue = value; },
          enumerable: true
        });
        return attr;
      };
      node.getElementsByTagNameNS = function(namespaceURI, localName) {
        if (!this.documentElement) return null;
        return this.documentElement.getElementsByTagNameNS(namespaceURI, localName);
      };
      node.getElementById = function(elementId) {
        if (this.documentElement) return null;
        var q = [this.documentElement];
        while (q.length > 0) {
          var current = q.shift();
          if (current.nodeType == Node.ELEMENT_NODE) {
            if (current.getAttribute("id") == elementId) return current;

            for (var i = 0; i < current.childNodes.length; ++i) {
              q.push(current.childNodes[i]);
            }
          }
        }
        return null;
      };
      node.onChildNodesChanged = function() {
        this.documentElement = null;
        for (var i = 0; i < this.childNodes.length; ++i) {
          if (this.childNodes[i].nodeType == Node.ELEMENT_NODE) {
            this.documentElement = this.childNodes[i];
          }
        }
      };

      Object.defineProperty(node, "textContent", {
        get: function() {
          return this.documentElement.textContent;
        },
        enumerable: true
      });
      return node;
    }

    function buildQualifiedName(name) {
      return name.prefix ? name.prefix + ":" + name.name : name.name;
    }

    this.parseFromString = function(s, mimeType) {
      var document = new Document();
      var elementsStack = [], currentElement = document;
      parseXml(s, {
        beginElement: function(name, attrs, isEmpty) {
           var parent = currentElement;
           elementsStack.push(parent);
           currentElement = document.createElementNS(name.namespace, buildQualifiedName(name));
           for (var i = 0; i < attrs.length; ++i) {
             var attr = document.createAttributeNS(attrs[i].name.namespace,
               buildQualifiedName(attrs[i].name));
             attr.value = attrs[i].value;
             currentElement.attributes.setNamedItem(attr);
           }
           parent.appendChild(currentElement);
           if (isEmpty) {
             currentElement = elementsStack.pop();
           }
        },
        endElement: function(name) {
          currentElement = elementsStack.pop();
        },
        text: function(text, isWhitespace) {
          var node = document.createTextNode(text);
          currentElement.appendChild(node);
        },
        cdata: function(text) {
          var node = document.createCDATASection(text);
          currentElement.appendChild(node);
        },
        comment: function(text) { },
        pi: function(name, attrs) { },
        doctype: function(text) { }
      });
      document.normalize();
      return document;
    };
  }

  var window = {
    appendChild: __elem_func__,
    removeChild: __empty_func__,
    addEventListener: __empty_func__,
    setInterval: __empty_func__,
    DOMParser: DOMParser,
    open: __empty_func__,
    document: document,
    createElement: function() { return elem; },
    setTimeout: __empty_func__,
    clearInterval: __empty_func__,
    localStorage: {},
    // required object types
    XMLHttpRequest: __empty_func__,
    HTMLImageElement: HTMLImageElement,
    HTMLCanvasElement: HTMLCanvasElement
  };

  window.XMLHttpRequest.prototype.open = __empty_func__;
  window.XMLHttpRequest.prototype.send = __empty_func__;
  window.XMLHttpRequest.prototype.responseText = "some text";
  window.XMLHttpRequest.prototype.setRequestHeader = __empty_func__;
  window.XMLHttpRequest.prototype.overrideMimeType = __empty_func__;
  window.XMLHttpRequest.prototype.status = 200;

  // and return our fake browser object
  return {
    isDomPresent: false,
    navigator: navigator,
    window: window,
    document: document,
    ajax: function(url) {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, false);
      if (xhr.overrideMimeType) {
        xhr.overrideMimeType("text/plain");
      }
      xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT");
      xhr.send(null);
      // failed request?
      if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); }
      return xhr.responseText;
    }
  };

}());
